package com.hl.encrypt_descrypt.encrypt;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.codec.binary.Base64;
import sun.security.pkcs.ContentInfo;
import sun.security.pkcs.PKCS7;
import sun.security.pkcs.SignerInfo;
import sun.security.x509.AlgorithmId;
import sun.security.x509.X500Name;

import javax.crypto.Cipher;
import javax.security.auth.x500.X500Principal;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Enumeration;


public class Test {

    public static final String RSA_PADDING_TYPE = "RSA/ECB/PKCS1Padding";


    /** 摘要算法 */
    private final static String digestAlgorithm = "SHA256";
    /** 签名算法 */
    private final static String signingAlgorithm = "SHA256withRSA";


    private static boolean matchUsage(boolean[] keyUsage, int usage) {
        if (0 == usage || null == keyUsage) {
            return true;
        }
        for (int i = 0; i < Math.min(keyUsage.length, 32); i++) {
            if ((usage & (1 << i)) != 0 && !keyUsage[i]) {
                return false;
            }
        }
        return true;
    }

    public static Object[] initSigner(String keyStorePath,
                                      String keyStorePassword, String keyPassword) throws Exception {
        KeyStore keyStore = null;
        if (keyStorePath.toLowerCase().endsWith(".p12")) {
            keyStore = keyStore.getInstance("PKCS12");
        } else {
            keyStore = keyStore.getInstance("JKS");
        }
        FileInputStream fis = null;
        try {
            fis = new FileInputStream(keyStorePath);
            keyStore.load(fis, keyStorePassword.toCharArray());
        } finally {
            if (null != fis) {
                fis.close();
            }
        }
        Enumeration<String> aliases = keyStore.aliases();
        String keyAlias = null;
        if (null != aliases) {
            while (aliases.hasMoreElements()) {
                keyAlias = (String) aliases.nextElement();
                Certificate[] certs = keyStore.getCertificateChain(keyAlias);
                if (null == certs || certs.length == 0) {
                    continue;
                }
                X509Certificate cert = (X509Certificate) certs[0];

                if (matchUsage(cert.getKeyUsage(), 1)) {
                    try {
                        cert.checkValidity();
                    } catch (CertificateException ex) {
                        continue;
                    }
                    break;
                }

            }
        }

        Object[] objects  = new Object[2];

        X509Certificate[] certificates = null;

        if (keyStore.isKeyEntry(keyAlias)) {
            Certificate[] certs = keyStore.getCertificateChain(keyAlias);
            for (int i = 0; i < certs.length; i++) {
                if (!(certs[i] instanceof X509Certificate)) {
                    throw new IllegalAccessError("证书链接所指的证书不符合x509格式！");
                }
            }

            certificates = new X509Certificate[certs.length];

            for (int i = 0; i < certs.length; i++) {
                certificates[i] = (X509Certificate) certs[i];
            }

        } else if (keyStore.isCertificateEntry(keyAlias)) {
            Certificate cert = keyStore.getCertificate(keyAlias);
            if (cert instanceof X509Certificate) {
                certificates = new X509Certificate[] { (X509Certificate) cert };
            }
        } else {

            throw new IllegalAccessError("keystore存储的证书不合法！");
        }

        objects[0] = (PrivateKey) keyStore.getKey(keyAlias, keyPassword.toCharArray());

        objects[1] = certificates;

        return objects;
    }

    /**
     * 取得签名工具，加载信任公钥证书
     *
     * @param publicKeyCertificatePath
     */

    public static Certificate initVerifier(String publicKeyCertificatePath)
            throws Exception {
        // 加载跟证书
        FileInputStream fis = null;
        Certificate publicKeyCertificate = null;
        try {
            fis = new FileInputStream(publicKeyCertificatePath);
            CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
            try {
                publicKeyCertificate = certificateFactory.generateCertificate(fis);
            } catch (Exception ex) {
                //InputStream is = new ByteArrayInputStream(new BASE64Decoder().decodeBuffer(fis));
                //publicKeyCertificate = certificateFactory.generateCertificate(is);
                //fixme
                ex.printStackTrace();
            }
        } finally {
            if (null != fis) {
                fis.close();
            }
        }
        return publicKeyCertificate;

    }

    public static byte[] encryptByPublicKey(byte[] data, PublicKey publicKey)
            throws Exception {
        Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");

        cipher.init(Cipher.ENCRYPT_MODE, publicKey);

        return cipher.doFinal(data);
    }

    public static byte[] decryptByPrivateKey(String cipherText, PrivateKey privateKey) throws Exception {

        byte[] sKey = Conver.decodeBase64(Conver.replaceBaseStr(cipherText));

        Cipher CIPHER = Cipher.getInstance(RSA_PADDING_TYPE);

        CIPHER.init(Cipher.DECRYPT_MODE, privateKey);

        return CIPHER.doFinal(sKey);
    }


    /**
     * 验证签名（无CRL（证书作废链））
     *
     * @param signature 签名
     * @param plainText 被签名数据
     * @param dn
     *            签名证书dn，如果为空则不做匹配验证
     */
    public static void p7DetacheVerifyMsg(String signature,
                                          String plainText, String dn) throws Exception {

        byte[] sign = Conver.decodeBase64(Conver.replaceBaseStr(signature));

        byte[] data = Conver.decodeBase64(Conver.replaceBaseStr(plainText));

        PKCS7 p7 = new PKCS7(sign);
        SignerInfo[] sis = null;
        try {
            // 验证签名本身，证书用法，证书扩展
            sis = p7.verify(data);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        if (null == sis) {
            throw new IllegalAccessError("keystore存储的证书不合法！");
        }
        Certificate publicKeyCertificate = initVerifier("D:\\BOCjm11.cer");

        try {
            for (int i = 0; i < sis.length; i++) {
                SignerInfo si = sis[i];
                X509Certificate cert = si.getCertificate(p7);
                // 证书是否过期
                // cert.checkValidity(date);
                cert.checkValidity();
                if (!cert.equals(publicKeyCertificate)) {
                    cert.verify(publicKeyCertificate.getPublicKey());
                }
                // 验证dn
                if (i == 0 && dn != null) {
                    X500Principal name = cert.getSubjectX500Principal();
                    if (!dn.equals(name.getName(X500Principal.RFC1779))
                            && !new X500Principal(dn).equals(name)) {
                        throw new SignatureException("签名证书"
                                + name.getName(X500Principal.RFC1779) + "不匹配["
                                + dn + "]");
                    }
                }
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }


    /**
     * 签名
     *
     * @param plainText 经base64编码后的原数据
     * @return signature 签名结果
     * @throws Exception
     */
    public static String p7DetacheSignMsg(String plainText, PrivateKey privateKey,X509Certificate[] certificates) throws Exception {
        byte[] data = Conver.decodeBase64(plainText);

        Signature signature = Signature.getInstance(signingAlgorithm);

        signature.initSign(privateKey);

        signature.update(data, 0, data.length);

        byte[] signedAttributes = signature.sign();

        // 根证书
        X509Certificate publicKey = certificates[0];

        SignerInfo signerInfo = new SignerInfo(new X500Name(publicKey
                .getIssuerX500Principal().getName()),
                publicKey.getSerialNumber(), AlgorithmId.get(digestAlgorithm),
                AlgorithmId.get(privateKey.getAlgorithm()), signedAttributes);

        PKCS7 pkcs7 = new PKCS7(
                new AlgorithmId[] { AlgorithmId.get(digestAlgorithm) },
                new ContentInfo(ContentInfo.DATA_OID, null),
                new X509Certificate[] { publicKey },
                new SignerInfo[] { signerInfo });

        ByteArrayOutputStream bout = new ByteArrayOutputStream();

        pkcs7.encodeSignedData(bout);

        java.util.Base64.Encoder encoder = java.util.Base64.getEncoder();
        String p7Siger = encoder.encodeToString(bout.toByteArray());
        return p7Siger;

    }

    /**
     * Base64编码
     * @param bytes 要编码的字节数组
     * @return String 编码后的字符串
     */
    public static String encodeBase64(byte[] bytes) {
        return Base64.encodeBase64String(bytes);
    }

    /**
     * Base64解码
     * @param conent 要解码的字符串
     * @return byte[] 解码后的字节数组
     */
    public static byte[] decodeBase64(String conent) {
        byte[] bytes = null;
        try {
            bytes = conent.getBytes("utf-8");
        } catch (UnsupportedEncodingException e) {
            bytes = conent.getBytes(Charset.defaultCharset());
        }
        bytes = Base64.decodeBase64(bytes);
        return bytes;
    }

    /**
     * 所用的到公私钥证书在cer目录下，自己放到任意目录下
     */
    public static void main(String[] args) throws Exception {

        String base64Str = encodeBase64("{credno:\"1234567\",credtype:\"01\", custName:\"asdc\" }".getBytes("utf-8"));


        Object[] objs = initSigner("F:\\青岛中行\\trunk\\Documents\\D5.Others\\cert\\BOCjm11.p12","BOCjm11","BOCjm11");
        PrivateKey privateKey = (PrivateKey)objs[0];
        X509Certificate[] certificates = (X509Certificate[])objs[1];

        String sign = p7DetacheSignMsg(base64Str, privateKey, certificates);

        System.out.println("签名:"+sign);

        String plainText = "{\"plainText\":\""+base64Str+"\",\"sign\":\""+sign+"\"}";

        System.out.println("明文最终报文："+plainText);

        //aes对称加密key
        String key = AesCBC.createSecretKey();

        System.out.println("aes对称加密明文key:"+key);


        String cipherText = AesCBC.encryptAES(plainText.getBytes("utf-8"), key);


        Certificate certificate = initVerifier("F:\\青岛中行\\trunk\\Documents\\D5.Others\\cert\\BOCjm11.cer");

        byte[] bytes = encryptByPublicKey(key.getBytes("utf-8"), certificate.getPublicKey());

        String skey = encodeBase64(bytes);

        System.out.println("密文key:"+skey);

        String retText = "{\"body\":\""+cipherText+"\",\"skey\":\""+skey+"\"}";

        System.out.println("加密后的最终报文:"+retText);

        JSONObject jsonObject = JSON.parseObject(retText);

        String mskey = (String)jsonObject.get("skey");

        //用私钥解密对称密钥
        byte[] mkbytes = decryptByPrivateKey(mskey,privateKey);

        mskey = new String(mkbytes, "utf-8");

        System.out.println("解密后的对称加密密钥:"+mskey);

        String mbody = (String)jsonObject.get("body");

        mbody = new String(AesCBC.decryptAES(mbody, mskey),"utf-8");

        System.out.println("解密后的报文:"+mbody);

        jsonObject = JSON.parseObject(mbody);

        mbody = (String)jsonObject.get("plainText");

        mbody = new String(decodeBase64(mbody),"utf-8");

        System.out.println("最终的明文报文体"+mbody);
    }

}
